Skip to content

Conversation

@adamamer20
Copy link
Member

@adamamer20 adamamer20 commented Aug 30, 2025

This PR simplifies the mental model and usage of AgentSet / AgentSetRegistry by:

  1. giving each AgentSet a name and enabling flexible lookup (by name, type, or index), and
  2. enforcing separation of concerns: the registry manages sets, while each set manages its own pl.DataFrame.

This yields a friendlier, clearer and safer API for everyday tasks (query one set, broadcast across sets).


What changed

1) Registry scope

  • Before: AgentSetRegistry.df returned a mapping {AgentSet → pl.DataFrame} and high-level helpers like get often operated directly on the sets’ underlying DataFrames, which was easy to misread as “registry-wide” operations.
  • After: the registry does not expose a df interface anymore, nor any “agents” iterator. You always work per set (via model.sets[...]) and then use that set’s API (.df, .get, .set, …). The registry orchestrates and routes, but never acts like a DataFrame holder.

Before → After: accessing DataFrames

Get one set’s frame

# BEFORE (The syntax is not very clear here, what are we obtaining at each step?)
frames = model.sets.df                   # {AgentSet -> pl.DataFrame} 
citizens_df = frames[model.sets[Citizens]]

# AFTER (clearly the df of the set)
citizens_df = model.sets["Citizens"].df

Iterate multiple sets’ frames

# BEFORE
frames = model.sets.df  # {AgentSet -> pl.DataFrame}

# AFTER (More flexibility in obtaining dfs)
frames = {s: s.df for s in model.sets}          # key by set object
# or, if you prefer labels:
frames_by_name = {s.name: s.df for s in model.sets}

2) Named sets & flexible lookup

model.sets["Citizens"]   # by name (string)
model.sets[Citizens]     # by type (class)
model.sets[0]            # by index (int)

Summary by CodeRabbit

  • New Features

    • Registry-centric agent sets: access sets via model.sets["Name"], batch actions via sets.do(...); AgentSet renaming and public name support.
    • Space APIs now accept AgentSet(s) for move/place/swap/neighbors/distance operations.
    • DataCollector reporters accept strings, string-collections, or callables at registry or set level with per-set column suffixing.
    • Model exposes seed property and a visible steps counter (m.steps).
  • Documentation

    • Guides and tutorials updated to the registry-based workflow and DataCollector patterns.
  • Refactor

    • API surfaces consolidated around AgentSet and registry abstractions for consistency.
  • Tests

    • New and updated tests covering registry, agent-set behaviors, reporters, and rename semantics; some legacy tests removed.

✏️ Tip: You can customize this high-level summary in your review settings.

…ement and enhance rename method for better delegation to AgentsDF.
…gent sets as a list for multiple matches and improve error messaging for better clarity.
…o 146-enhancement-consider-using-a-key-based-structure-for-agentsets-instead-of-list-in-agentsdf
…ency and clarity in the abstract class naming.
…proved consistency and clarity; enhance error messaging in __getitem__ and rename methods for better readability.
@codecov
Copy link

codecov bot commented Aug 30, 2025

Codecov Report

❌ Patch coverage is 83.88792% with 92 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.23%. Comparing base (658be7a) to head (b8149cc).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
mesa_frames/concrete/agentsetregistry.py 86.18% 38 Missing ⚠️
mesa_frames/concrete/datacollector.py 48.64% 38 Missing ⚠️
mesa_frames/abstract/agentset.py 93.15% 5 Missing ⚠️
mesa_frames/abstract/agentsetregistry.py 93.10% 4 Missing ⚠️
mesa_frames/concrete/agentset.py 92.72% 4 Missing ⚠️
mesa_frames/concrete/model.py 75.00% 2 Missing ⚠️
mesa_frames/abstract/space.py 93.75% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #172      +/-   ##
==========================================
- Coverage   92.54%   89.23%   -3.31%     
==========================================
  Files          14       14              
  Lines        1717     2007     +290     
==========================================
+ Hits         1589     1791     +202     
- Misses        128      216      +88     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

…nt set types, improving performance and clarity in KeyError messages.
…arity in AGENTS.md, agents.py, agentset.py, and test_sets_accessor.py; improve formatting in test cases.
…r-agentsets-instead-of-list-in-agentsdf' of https://github.com/projectmesa/mesa-frames into 146-enhancement-consider-using-a-key-based-structure-for-agentsets-instead-of-list-in-agentsdf
…cessor; update parameter descriptions for improved understanding.
…update conflict resolution and mode descriptions for improved understanding.
…cessor and AgentSetsAccessor; update default values and descriptions for parameters.
adamamer20 and others added 5 commits October 20, 2025 10:10
…multiple files

feat: add seed property to Model for better random generator management
docs: update docstrings in AbstractDataCollector for consistency and clarity
…tructure-for-agentsets-instead-of-list-in-agentsdf
@adamamer20 adamamer20 requested a review from Ben-geo October 28, 2025 17:51
…port cycles

feat: add typing-extensions dependency for enhanced type support
…r-agentsets-instead-of-list-in-agentsdf' of https://github.com/projectmesa/mesa-frames into 146-enhancement-consider-using-a-key-based-structure-for-agentsets-instead-of-list-in-agentsdf
…tructure-for-agentsets-instead-of-list-in-agentsdf
@Ben-geo
Copy link
Collaborator

Ben-geo commented Nov 29, 2025

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 29, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
mesa_frames/concrete/model.py (1)

97-100: Duplicate steps property definition — remove one.

The steps property is defined twice: here (lines 97–100) and again at lines 154–163. The second definition will silently override this one. Remove the duplicate.

-    @property
-    def steps(self) -> int:
-        """Get the current step count."""
-        return self._steps
-
     def reset_randomizer(self, seed: int | Sequence[int] | None) -> None:

Keep the more complete definition at lines 154–163.

♻️ Duplicate comments (1)
mesa_frames/concrete/datacollector.py (1)

555-556: Same bare Exception catch as above.

Apply the same fix here when extracting the helper.

🧹 Nitpick comments (7)
mesa_frames/concrete/datacollector.py (2)

181-189: Duplicated helper function — consider extracting to module level.

_is_str_collection is defined identically here and again at lines 548–556. Extract it once at module scope to reduce duplication.

+def _is_str_collection(x: Any) -> bool:
+    """Return True if x is a non-string collection of strings."""
+    if isinstance(x, str):
+        return False
+    try:
+        from collections.abc import Collection
+        return isinstance(x, Collection) and all(isinstance(i, str) for i in x)
+    except TypeError:
+        return False
+
+
 class DataCollector(AbstractDataCollector):

Then remove the nested definitions at lines 181–189 and 548–556.


188-189: Narrow exception catch to TypeError.

The all(isinstance(...)) call would raise TypeError if iteration fails. Catching bare Exception masks unrelated bugs.

-            except Exception:
-                return False
+            except TypeError:
+                return False
mesa_frames/abstract/agentset.py (1)

51-51: Annotate mutable class attribute with ClassVar.

_copy_only_reference is a mutable list at class level. Mark it with ClassVar to signal it's not an instance attribute.

+from typing import ClassVar
+
 class AbstractAgentSet(CopyMixin, DataFrameMixin):
-    _copy_only_reference: list[str] = ["_model"]
+    _copy_only_reference: ClassVar[list[str]] = ["_model"]
mesa_frames/concrete/agentset.py (1)

588-592: Redundant name check in __getattr__.

The if key == "name" check on lines 589-590 is unnecessary. Since name is defined as a property (lines 674-677), Python's attribute resolution will find the property before falling through to __getattr__. This branch will never execute.

Apply this diff to remove the dead branch:

     def __getattr__(self, key: str) -> Any:
-        if key == "name":
-            return self.name
         super().__getattr__(key)
         return self._df[key]
mesa_frames/concrete/agentsetregistry.py (2)

149-159: Unused variable single.

The variable single is assigned at lines 153 and 156 but never used. This appears to be leftover from removed functionality.

Apply this diff to remove the unused variable:

         if isinstance(target, (AgentSet, str)):
             if new_name is None:
                 raise TypeError("new_name must be provided for single rename")
             pairs_idx: list[tuple[int, str]] = [(_resolve_one(target), new_name)]
-            single = True
         elif isinstance(target, dict):
             pairs_idx = [(_resolve_one(k), v) for k, v in target.items()]
-            single = False
         else:
             pairs_idx = [(_resolve_one(k), v) for k, v in target]
-            single = False

244-256: Add exception chaining for clearer error context.

When re-raising KeyError as a new KeyError, adding from err (or from None) makes the traceback clearer by showing the relationship between exceptions.

Apply this diff:

             def _find_index_by_name(name: str) -> int:
                 try:
                     return name_to_idx[name]
                 except KeyError:
-                    raise KeyError(f"Agent set '{name}' not found")
+                    raise KeyError(f"Agent set '{name}' not found") from None
         else:
 
             def _find_index_by_name(name: str) -> int:
                 for i, s in enumerate(obj._agentsets):
                     if s.name == name:
                         return i
 
-                raise KeyError(f"Agent set '{name}' not found")
+                raise KeyError(f"Agent set '{name}' not found") from None
mesa_frames/abstract/agentsetregistry.py (1)

584-626: Note: keys() and items() silently skip sets with None names.

When using key_by="name", sets with name=None are silently excluded from the iteration (lines 598-599 and 621-622). This could lead to unexpected behavior if some AgentSets have None names. Consider documenting this behavior in the docstrings.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4ef1a25 and 938056f.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • examples/sugarscape_ig/ss_polars/agents.py (3 hunks)
  • mesa_frames/abstract/agentset.py (12 hunks)
  • mesa_frames/abstract/agentsetregistry.py (11 hunks)
  • mesa_frames/abstract/datacollector.py (6 hunks)
  • mesa_frames/concrete/agentset.py (6 hunks)
  • mesa_frames/concrete/agentsetregistry.py (6 hunks)
  • mesa_frames/concrete/datacollector.py (3 hunks)
  • mesa_frames/concrete/model.py (3 hunks)
  • mesa_frames/types_.py (2 hunks)
  • pyproject.toml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • mesa_frames/abstract/datacollector.py
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-04-29T09:25:34.183Z
Learnt from: adamamer20
Repo: projectmesa/mesa-frames PR: 143
File: mesa_frames/abstract/space.py:50-63
Timestamp: 2025-04-29T09:25:34.183Z
Learning: The project mesa-frames has been upgraded to Python 3.11, which provides native support for `Self` type in the standard typing module, eliminating the need for imports from typing_extensions.

Applied to files:

  • pyproject.toml
  • mesa_frames/concrete/agentset.py
  • mesa_frames/types_.py
📚 Learning: 2025-09-19T15:01:35.063Z
Learnt from: adamamer20
Repo: projectmesa/mesa-frames PR: 172
File: mesa_frames/abstract/agentset.py:460-470
Timestamp: 2025-09-19T15:01:35.063Z
Learning: In mesa-frames codebase, avoid using TYPE_CHECKING guards for type annotations because the project uses beartype for runtime type checking, which requires the actual type objects to be available at runtime. TYPE_CHECKING guards would hide these imports from runtime execution, breaking beartype compatibility.

Applied to files:

  • mesa_frames/abstract/agentset.py
  • mesa_frames/concrete/agentset.py
  • mesa_frames/types_.py
  • mesa_frames/abstract/agentsetregistry.py
📚 Learning: 2025-04-29T09:20:40.575Z
Learnt from: adamamer20
Repo: projectmesa/mesa-frames PR: 143
File: mesa_frames/concrete/agentset.py:88-88
Timestamp: 2025-04-29T09:20:40.575Z
Learning: When using `from __future__ import annotations` at the top of a file, forward references in type annotations do not need to be enclosed in strings, even when using runtime type checking with beartype. Tools like pyupgrade deliberately remove such quoted annotations. Ruff may still generate F821 warnings for unquoted imports, but these can be safely ignored.

Applied to files:

  • mesa_frames/abstract/agentset.py
  • mesa_frames/concrete/agentset.py
  • mesa_frames/abstract/agentsetregistry.py
📚 Learning: 2025-04-29T09:23:26.204Z
Learnt from: adamamer20
Repo: projectmesa/mesa-frames PR: 143
File: mesa_frames/abstract/mixin.py:407-407
Timestamp: 2025-04-29T09:23:26.204Z
Learning: In the mesa-frames project, overload type annotations in abstract classes are used to define multiple method signatures for static type checking purposes, but they don't affect runtime behavior. Method implementations in concrete classes determine the actual behavior and default values.

Applied to files:

  • mesa_frames/types_.py
🧬 Code graph analysis (2)
mesa_frames/concrete/agentset.py (3)
mesa_frames/abstract/agentset.py (4)
  • model (523-531)
  • name (512-520)
  • rename (556-577)
  • index (475-483)
mesa_frames/concrete/agentsetregistry.py (1)
  • rename (116-219)
mesa_frames/abstract/mixin.py (1)
  • _get_obj (139-155)
mesa_frames/concrete/agentsetregistry.py (2)
mesa_frames/abstract/agentsetregistry.py (13)
  • AbstractAgentSetRegistry (64-668)
  • get (285-287)
  • get (291-293)
  • get (297-301)
  • get (305-315)
  • get (318-329)
  • contains (169-176)
  • contains (180-187)
  • contains (190-203)
  • do (207-216)
  • do (220-234)
  • do (237-281)
  • ids (660-668)
mesa_frames/abstract/mixin.py (2)
  • _get_obj (139-155)
  • copy (73-137)
🪛 Ruff (0.14.6)
mesa_frames/abstract/agentset.py

51-51: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


523-523: Undefined name mesa_frames

(F821)


545-545: Undefined name mesa_frames

(F821)

mesa_frames/concrete/agentset.py

86-86: Undefined name mesa_frames

(F821)


319-319: Avoid specifying long messages outside the exception class

(TRY003)

mesa_frames/types_.py

104-108: Undefined name "mesa_frames.abstract.agentset.AbstractAgentSet | " "type[mesa_frames.abstract.agentset.AbstractAgentSet] | " "str | Collection[" "mesa_frames.abstract.agentset.AbstractAgentSet | " "type[mesa_frames.abstract.agentset.AbstractAgentSet] | str] | None"

(F821)


104-108: Undefined name "mesa_frames.abstract.agentset.AbstractAgentSet | " "type[mesa_frames.abstract.agentset.AbstractAgentSet] | " "str | Collection[" "mesa_frames.abstract.agentset.AbstractAgentSet | " "type[mesa_frames.abstract.agentset.AbstractAgentSet] | str] | None"

(F821)


104-108: Undefined name "mesa_frames.abstract.agentset.AbstractAgentSet | " "type[mesa_frames.abstract.agentset.AbstractAgentSet] | " "str | Collection[" "mesa_frames.abstract.agentset.AbstractAgentSet | " "type[mesa_frames.abstract.agentset.AbstractAgentSet] | str] | None"

(F821)


104-108: Undefined name "mesa_frames.abstract.agentset.AbstractAgentSet | " "type[mesa_frames.abstract.agentset.AbstractAgentSet] | " "str | Collection[" "mesa_frames.abstract.agentset.AbstractAgentSet | " "type[mesa_frames.abstract.agentset.AbstractAgentSet] | str] | None"

(F821)


115-119: Undefined name "mesa_frames.concrete.agentset.AgentSet | " "type[mesa_frames.concrete.agentset.AgentSet] | " "str | Collection[" "mesa_frames.concrete.agentset.AgentSet | " "type[mesa_frames.concrete.agentset.AgentSet] | str] | None"

(F821)


115-119: Undefined name "mesa_frames.concrete.agentset.AgentSet | " "type[mesa_frames.concrete.agentset.AgentSet] | " "str | Collection[" "mesa_frames.concrete.agentset.AgentSet | " "type[mesa_frames.concrete.agentset.AgentSet] | str] | None"

(F821)


115-119: Undefined name "mesa_frames.concrete.agentset.AgentSet | " "type[mesa_frames.concrete.agentset.AgentSet] | " "str | Collection[" "mesa_frames.concrete.agentset.AgentSet | " "type[mesa_frames.concrete.agentset.AgentSet] | str] | None"

(F821)


115-119: Undefined name "mesa_frames.concrete.agentset.AgentSet | " "type[mesa_frames.concrete.agentset.AgentSet] | " "str | Collection[" "mesa_frames.concrete.agentset.AgentSet | " "type[mesa_frames.concrete.agentset.AgentSet] | str] | None"

(F821)


123-137: __all__ is not sorted

Apply an isort-style sorting to __all__

(RUF022)

mesa_frames/concrete/datacollector.py

188-188: Do not catch blind exception: Exception

(BLE001)


239-241: Abstract raise to an inner function

(TRY301)


239-241: Avoid specifying long messages outside the exception class

(TRY003)


258-260: Avoid specifying long messages outside the exception class

(TRY003)


264-266: Avoid specifying long messages outside the exception class

(TRY003)


555-555: Do not catch blind exception: Exception

(BLE001)

mesa_frames/abstract/agentsetregistry.py

102-102: Undefined name mesa_frames

(F821)


104-104: Undefined name mesa_frames

(F821)


105-105: Undefined name mesa_frames

(F821)


146-146: Undefined name mesa_frames

(F821)


147-147: Undefined name mesa_frames

(F821)


172-172: Undefined name mesa_frames

(F821)


173-173: Undefined name mesa_frames

(F821)


183-183: Undefined name mesa_frames

(F821)


184-184: Undefined name mesa_frames

(F821)


233-233: Undefined name mesa_frames

(F821)


251-251: Undefined name mesa_frames

(F821)


287-287: Undefined name mesa_frames

(F821)


293-293: Undefined name mesa_frames

(F821)


299-299: Undefined name mesa_frames

(F821)


301-301: Undefined name mesa_frames

(F821)


307-307: Undefined name mesa_frames

(F821)


308-308: Undefined name mesa_frames

(F821)


309-309: Undefined name mesa_frames

(F821)


312-312: Undefined name mesa_frames

(F821)


313-313: Undefined name mesa_frames

(F821)


320-320: Undefined name mesa_frames

(F821)


321-321: Undefined name mesa_frames

(F821)


322-322: Undefined name mesa_frames

(F821)


325-325: Undefined name mesa_frames

(F821)


326-326: Undefined name mesa_frames

(F821)


357-357: Undefined name mesa_frames

(F821)


358-358: Undefined name mesa_frames

(F821)


428-428: Undefined name mesa_frames

(F821)


429-429: Undefined name mesa_frames

(F821)


435-435: Undefined name mesa_frames

(F821)


443-443: Undefined name mesa_frames

(F821)


448-448: Undefined name mesa_frames

(F821)


452-452: Undefined name mesa_frames

(F821)


453-453: Undefined name mesa_frames

(F821)


456-456: Undefined name mesa_frames

(F821)


458-458: Undefined name mesa_frames

(F821)


459-459: Undefined name mesa_frames

(F821)


466-466: Undefined name mesa_frames

(F821)


467-467: Undefined name mesa_frames

(F821)


487-487: Undefined name mesa_frames

(F821)


488-488: Undefined name mesa_frames

(F821)


508-508: Undefined name mesa_frames

(F821)


509-509: Undefined name mesa_frames

(F821)


529-529: Undefined name mesa_frames

(F821)


540-540: Avoid specifying long messages outside the exception class

(TRY003)


553-553: Avoid specifying long messages outside the exception class

(TRY003)


560-560: Undefined name mesa_frames

(F821)


586-586: Undefined name mesa_frames

(F821)


596-596: Avoid specifying long messages outside the exception class

(TRY003)


605-605: Undefined name mesa_frames

(F821)


606-606: Undefined name mesa_frames

(F821)


619-619: Avoid specifying long messages outside the exception class

(TRY003)


624-624: Undefined name mesa_frames

(F821)

mesa_frames/concrete/agentsetregistry.py

88-90: Avoid specifying long messages outside the exception class

(TRY003)


142-142: Avoid specifying long messages outside the exception class

(TRY003)


147-147: Avoid specifying long messages outside the exception class

(TRY003)


151-151: Avoid specifying long messages outside the exception class

(TRY003)


159-159: Local variable single is assigned to but never used

Remove assignment to unused variable single

(F841)


191-191: zip() without an explicit strict= parameter

Add explicit value for parameter strict=

(B905)


248-248: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


248-248: Avoid specifying long messages outside the exception class

(TRY003)


256-256: Avoid specifying long messages outside the exception class

(TRY003)


265-265: Avoid specifying long messages outside the exception class

(TRY003)


267-269: Avoid specifying long messages outside the exception class

(TRY003)


275-277: Avoid specifying long messages outside the exception class

(TRY003)


281-283: Avoid specifying long messages outside the exception class

(TRY003)


291-293: Avoid specifying long messages outside the exception class

(TRY003)


304-306: Avoid specifying long messages outside the exception class

(TRY003)


309-309: Avoid specifying long messages outside the exception class

(TRY003)


385-385: Avoid specifying long messages outside the exception class

(TRY003)


438-440: Avoid specifying long messages outside the exception class

(TRY003)


449-452: Avoid specifying long messages outside the exception class

(TRY003)


467-469: Avoid specifying long messages outside the exception class

(TRY003)


650-650: Avoid specifying long messages outside the exception class

(TRY003)


692-694: Avoid specifying long messages outside the exception class

(TRY003)


735-735: Avoid specifying long messages outside the exception class

(TRY003)


738-738: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (23)
examples/sugarscape_ig/ss_polars/agents.py (2)

37-46: eat correctly scopes sugar updates to this AgentSet; double‑check ID alignment and is_in semantics

Filtering self.space.cells down to rows whose agent_id is in self.index.implode() is a good fix to avoid cross‑set sugar mutations — only agents belonging to this set now get their sugar updated and later discarded based on sugar <= 0.

Two points to verify:

  • That self.space.cells["agent_id"] uses the same ID space as self.index (e.g. the same unique_id field, not row offsets), otherwise some agents could be skipped or mis‑included.
  • That cells["agent_id"].is_in(self.index.implode()) behaves as intended with your current Polars version; using implode() here produces a list‑typed Series literal, which is a bit non‑obvious. If the goal is simple membership against all indices, you may want to confirm via a small test (or consider a more explicit pattern like building a Python set or using a plain Series if supported).

203-205: Confirm .implode() usage in is_in for best_moves["agent_id_center"]

The logic in get_best_moves still matches the intent described in the comments (agents can move when their blocking agent has already moved, and then we drop agents that now have a best move). The new checks

  • Line 203–205: pl.col("blocking_agent_id").is_in(best_moves["agent_id_center"].implode())
  • Line 215–217: pl.col("agent_id_center").is_in(best_moves["agent_id_center"].implode())

depend on how Polars Expr.is_in interacts with a list‑typed Series produced by .implode().

To avoid subtle bugs, especially as Polars evolves, it would be good to:

  • Explicitly verify (with a focused test) that these is_in calls behave exactly as “membership in the set of all current agent_id_center values” when best_moves has multiple rows.
  • Consider adding a short comment near the first use explaining that best_moves["agent_id_center"].implode() is intentionally used as a list literal for membership, not as a per‑row list column, so future readers don’t “simplify” it back to the old expression.

Also applies to: 215-217

pyproject.toml (1)

41-41: LGTM! Dependency needed for TypeAliasType compatibility.

typing-extensions>=4.15.0 correctly provides TypeAliasType for Python 3.11 (where it's not yet in the stdlib). This enables the lazy type alias pattern used in mesa_frames/types_.py.

mesa_frames/concrete/datacollector.py (1)

216-261: LGTM! Callable reporter handling with proper fallback.

The registry-level → set-level fallback pattern is well-structured. The TypeError catch (line 243) correctly handles signature mismatches without swallowing other exceptions.

mesa_frames/types_.py (2)

123-137: LGTM! Public API surface correctly exported.

The selectors and common type aliases are properly exposed. The static analysis warning about sorting is a style preference.


101-121: TypeAliasType string lazy resolution is correctly implemented and verified to work with beartype.

The code uses TypeAliasType with string-based forward references (lines 101–121 in mesa_frames/types_.py) to avoid circular import cycles. This pattern is:

  1. Supported: typing-extensions>=4.15.0 (specified in pyproject.toml) provides full TypeAliasType and evaluate_forward_ref support for lazy string resolution.

  2. Beartype-compatible: beartype's runtime type-checking pipeline resolves stringified type aliases at validation time. The codebase confirms this works—beartype is integrated via beartype_this_package() in the dev environment (MESA_FRAMES_RUNTIME_TYPECHECKING = "true"), and the AgentSetSelector and AbstractAgentSetSelector aliases are actively used in function signatures throughout the codebase without reported failures.

  3. Documented and intentional: The comment block (lines 93–99) correctly explains the strategy. The full module paths in strings ("mesa_frames.abstract.agentset.AbstractAgentSet", etc.) are necessary to allow lazy resolution without eager imports.

No issues found.

mesa_frames/abstract/agentset.py (3)

126-128: LGTM! Clean discard semantics using suppress.

Using contextlib.suppress for the "remove if exists" pattern is idiomatic and avoids nested try/except.


365-371: LGTM! Subtraction operators correctly use discard for safe removal.

Using discard ensures that removing non-existent agents doesn't raise errors, which aligns with standard set semantics.


522-553: LGTM! Model context properties well-designed.

The model, random, and space properties provide clean access to the parent model's resources. The F821 warnings are expected due to beartype runtime requirements (per learnings).

mesa_frames/concrete/model.py (3)

89-95: LGTM! Step-counting wrapper pattern.

The wrapper captures the user-defined step() and auto-increments _steps. This works correctly because subclass overrides exist before super().__init__() completes.


116-136: LGTM! Seed property with proper delegation.

The setter correctly delegates to reset_randomizer, maintaining a single source of truth for RNG initialization.


146-152: LGTM! Step delegation through registry.

Using self.sets.do("step") aligns with the registry-first design. This correctly propagates step() to all contained AgentSet instances.

mesa_frames/concrete/agentset.py (3)

85-105: LGTM on constructor updates.

The initialization properly sets up the _name attribute with a sensible default (class name) when not provided. The docstring accurately documents the new name parameter.

Based on learnings, the F821 warning for mesa_frames on line 86 is expected and can be safely ignored due to beartype runtime type checking requirements.


256-290: LGTM on do() method implementation.

The masking logic correctly handles both full-mask (direct operation) and partial-mask (copy-operate-concatenate) cases. The inplace attribute copying at lines 283-286 properly propagates state changes back to self when operating on a masked subset.


309-321: LGTM on remove() method.

The implementation correctly validates that all specified agents exist before removal and provides a clear error message when they don't. The early return for empty/None inputs is a good optimization.

mesa_frames/concrete/agentsetregistry.py (6)

80-114: LGTM on add() method with unique name handling.

The implementation correctly ensures unique names across the registry by auto-incrementing suffixes when conflicts occur. The ID uniqueness validation and proper extension of the agentsets list are well-implemented.


411-482: LGTM on do() method implementation.

The method correctly handles registry indices (lines 427, 436, 465), guards against duplicate type keys when key_by='type' (lines 448-452), and properly preserves untouched sets when operating on a subset (lines 479-480). This addresses the issues flagged in previous reviews.


340-385: LGTM on contains() method.

The implementation provides efficient fast paths for single-value lookups and properly handles mixed iterable inputs. The early returns for empty iterables and the type-based dispatch are well-structured.


626-658: LGTM on _resolve_selector() helper.

The method correctly handles all selector types (None, instance, type, name, and collections) with proper deduplication that preserves insertion order. The TypeError for unsupported element types provides good error feedback.


611-624: LGTM on _recompute_ids() helper.

The method correctly rebuilds the registry-level unique_id cache from the current AgentSets, handling both empty and non-empty states appropriately.


718-738: LGTM on __getitem__() implementation.

The method correctly handles all three access patterns (by index, name, and type) with appropriate error handling and return types matching the overload signatures.

mesa_frames/abstract/agentsetregistry.py (2)

526-553: LGTM on __setitem__() implementation.

The method correctly handles both index and name-based assignment, properly validates model ownership, and delegates to replace() for existing entries or add() for new name-based entries. The rename before add ensures the new set has the correct name.


658-667: LGTM on ids property definition.

The abstract property provides a clear contract for accessing all agent unique_id values across contained sets.

@Ben-geo
Copy link
Collaborator

Ben-geo commented Nov 29, 2025

@adamamer20 I also imported mesa_frames where it is being used.
It made it a bit cleaner!

Copy link
Collaborator

@Ben-geo Ben-geo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! - Other than the first coderabbit suggestion!

adamamer20 and others added 4 commits December 3, 2025 19:15
…tructure-for-agentsets-instead-of-list-in-agentsdf
…r-agentsets-instead-of-list-in-agentsdf' of https://github.com/projectmesa/mesa-frames into 146-enhancement-consider-using-a-key-based-structure-for-agentsets-instead-of-list-in-agentsdf
@Ben-geo Ben-geo mentioned this pull request Dec 5, 2025
@adamamer20 adamamer20 merged commit 3037456 into main Dec 8, 2025
12 of 14 checks passed
@adamamer20 adamamer20 deleted the 146-enhancement-consider-using-a-key-based-structure-for-agentsets-instead-of-list-in-agentsdf branch December 8, 2025 18:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking Changes that break backwards compatibility or require major updates. enhancement Improvements to existing features or performance.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Enhancement]: Consider using a key-based structure for agentsets instead of list in AgentsDf

3 participants